package org.ricks.log;

import org.ricks.actor.ActorMgr;
import org.ricks.actor.ActorRunnable;
import org.ricks.common.conf.LogConfig;


/**
 * Static logger for issuing log entries.
 */
public final class Logger {

	private static final int STACKTRACE_DEPTH = 2;

	private static final AdvancedMessageFormatter formatter = new AdvancedMessageFormatter(LogConfig.isEscapingEnabled());

	private static final TinylogLoggingProvider provider = new TinylogLoggingProvider();

	// @formatter:off
	public static final boolean MINIMUM_LEVEL_COVERS_TRACE = isCoveredByMinimumLevel(Level.TRACE);
	public static final boolean MINIMUM_LEVEL_COVERS_DEBUG = isCoveredByMinimumLevel(Level.DEBUG);
	public static final boolean MINIMUM_LEVEL_COVERS_INFO  = isCoveredByMinimumLevel(Level.INFO);
	public static final boolean MINIMUM_LEVEL_COVERS_WARN  = isCoveredByMinimumLevel(Level.WARN);
	public static final boolean MINIMUM_LEVEL_COVERS_ERROR = isCoveredByMinimumLevel(Level.ERROR);
	// @formatter:on

	/**
	 * Logs a message at {@link Level#TRACE TRACE} level.
	 *
	 * @param message
	 *            String or any other object with a meaningful {@link #toString()} method
	 */
	public static void trace(final Object message) {
		if (MINIMUM_LEVEL_COVERS_TRACE) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.TRACE, null, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs a formatted message at {@link Level#TRACE TRACE} level. "{}" placeholders will be replaced by given
	 * arguments.
	 *
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void trace(final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_TRACE) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.TRACE, null, formatter, message, arguments)));
		}
	}


	/**
	 * Logs an exception at {@link Level#TRACE TRACE} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 */
	public static void trace(final Throwable exception) {
		if (MINIMUM_LEVEL_COVERS_TRACE) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.TRACE, exception, null, null, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a custom message at {@link Level#TRACE TRACE} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Text message to log
	 */
	public static void trace(final Throwable exception, final String message) {
		if (MINIMUM_LEVEL_COVERS_TRACE) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.TRACE, exception, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a formatted custom message at {@link Level#TRACE TRACE} level. "{}" placeholders will be
	 * replaced by given arguments.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void trace(final Throwable exception, final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_TRACE) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.TRACE, exception, formatter, message, arguments)));
		}
	}


	/**
	 * Logs a message at {@link Level#DEBUG DEBUG} level.
	 *
	 * @param message
	 *            String or any other object with a meaningful {@link #toString()} method
	 */
	public static void debug(final Object message) {
		if (MINIMUM_LEVEL_COVERS_DEBUG) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.DEBUG, null, null, message, (Object[]) null)));
		}
	}


	/**
	 * Logs a formatted message at {@link Level#DEBUG DEBUG} level. "{}" placeholders will be replaced by given
	 * arguments.
	 *
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void debug(final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_DEBUG) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.DEBUG, null, formatter, message, arguments)));
		}
	}

	/**
	 * Logs an exception at {@link Level#DEBUG DEBUG} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 */
	public static void debug(final Throwable exception) {
		if (MINIMUM_LEVEL_COVERS_DEBUG) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.DEBUG, exception, null, null, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a custom message at {@link Level#DEBUG DEBUG} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Text message to log
	 */
	public static void debug(final Throwable exception, final String message) {
		if (MINIMUM_LEVEL_COVERS_DEBUG) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.DEBUG, exception, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a formatted custom message at {@link Level#DEBUG DEBUG} level. "{}" placeholders will be
	 * replaced by given arguments.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void debug(final Throwable exception, final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_DEBUG) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.DEBUG, exception, formatter, message, arguments)));
		}
	}

	/**
	 * Logs a message at {@link Level#INFO INFO} level.
	 *
	 * @param message
	 *            String or any other object with a meaningful {@link #toString()} method
	 */
	public static void info(final Object message) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.INFO, null, null, message, (Object[]) null)));
		}
	}


	/**
	 * Logs a formatted message at {@link Level#INFO INFO} level. "{}" placeholders will be replaced by given arguments.
	 *
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void info(final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.INFO, null, formatter, message, arguments)));
		}
	}

	public static void info(final String message, final boolean isBase) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, isBase, Level.INFO, null, formatter, message)));
		}
	}

	/**
	 * Logs an exception at {@link Level#INFO INFO} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 */
	public static void info(final Throwable exception) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.INFO, exception, null, null, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a custom message at {@link Level#INFO INFO} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Text message to log
	 */
	public static void info(final Throwable exception, final String message) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.INFO, exception, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a formatted custom message at {@link Level#INFO INFO} level. "{}" placeholders will be
	 * replaced by given arguments.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void info(final Throwable exception, final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_INFO) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.INFO, exception, formatter, message, arguments)));
		}
	}

	public static void warn(final Object message) {
		if (MINIMUM_LEVEL_COVERS_WARN) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			provider.log(currThread,stackTraceElement, false, Level.WARN, null, null, message, (Object[]) null);
		}
	}

	public static void warn(final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_WARN) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.WARN, null, formatter, message, arguments)));
		}
	}

	/**
	 * Logs an exception at {@link Level#WARN WARN} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 */
	public static void warn(final Throwable exception) {
		if (MINIMUM_LEVEL_COVERS_WARN) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.WARN, exception, null, null, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a custom message at {@link Level#WARN WARN} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Text message to log
	 */
	public static void warn(final Throwable exception, final String message) {
		if (MINIMUM_LEVEL_COVERS_WARN) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.WARN, exception, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a formatted custom message at {@link Level#WARN WARN} level. "{}" placeholders will
	 * be replaced by given arguments.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void warn(final Throwable exception, final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_WARN) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.WARN, exception, formatter, message, arguments)));
		}
	}

	/**
	 * Logs a message at {@link Level#ERROR ERROR} level.
	 *
	 * @param message
	 *            String or any other object with a meaningful {@link #toString()} method
	 */
	public static void error(final Object message) {
		if (MINIMUM_LEVEL_COVERS_ERROR) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.ERROR, null, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs a formatted message at {@link Level#ERROR ERROR} level. "{}" placeholders will be replaced by given
	 * arguments.
	 *
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void error(final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_ERROR) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.ERROR, null, formatter, message, arguments)));
		}
	}

	/**
	 * Logs an exception at {@link Level#ERROR ERROR} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 */
	public static void error(final Throwable exception) {
		if (MINIMUM_LEVEL_COVERS_ERROR) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.ERROR, exception, null, null, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a custom message at {@link Level#ERROR ERROR} level.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Text message to log
	 */
	public static void error(final Throwable exception, final String message) {
		if (MINIMUM_LEVEL_COVERS_ERROR) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.ERROR, exception, null, message, (Object[]) null)));
		}
	}

	/**
	 * Logs an exception with a formatted custom message at {@link Level#ERROR ERROR} level. "{}" placeholders will be
	 * replaced by given arguments.
	 *
	 * @param exception
	 *            Caught exception or any other throwable to log
	 * @param message
	 *            Formatted text message to log
	 * @param arguments
	 *            Arguments for formatted text message
	 */
	public static void error(final Throwable exception, final String message, final Object... arguments) {
		if (MINIMUM_LEVEL_COVERS_ERROR) {
			Thread currThread = Thread.currentThread();
			StackTraceElement stackTraceElement = RuntimeProvider.getCallerStackTraceElement(STACKTRACE_DEPTH );
			ActorMgr.get().getLogActor().execute(new ActorRunnable("log",() -> provider.log(currThread,stackTraceElement, false, Level.ERROR, exception, formatter, message, arguments)));
		}
	}

	/**
	 * Checks if a given severity level is covered by the logging provider's minimum level.
	 *
	 * @param level
	 *            Severity level to check
	 * @return {@code true} if given severity level is covered, otherwise {@code false}
	 */
	private static boolean isCoveredByMinimumLevel(final Level level) {
		return LogConfig.getLevel().ordinal() <= level.ordinal();
	}

}
